<?php

namespace Dongasai\WkProtoPhp;

/**
 * 二进制解码器
 */
class Decoder
{
    private string $data;
    private int $offset = 0;

    public function __construct(string $data)
    {
        $this->data = $data;
    }

    /**
     * 获取剩余长度
     */
    public function len(): int
    {
        return strlen($this->data) - $this->offset;
    }

    /**
     * 读取uint8
     */
    public function uint8(): int
    {
        if ($this->offset + 1 > strlen($this->data)) {
            throw new \RuntimeException(sprintf('Decoder couldn\'t read expect bytes %d of %d', $this->offset + 1, strlen($this->data)));
        }
        
        $value = ord($this->data[$this->offset]);
        $this->offset += 1;
        return $value;
    }

    /**
     * 读取int16
     */
    public function int16(): int
    {
        if ($this->offset + 2 > strlen($this->data)) {
            throw new \RuntimeException(sprintf('Decoder couldn\'t read expect bytes %d of %d', $this->offset + 2, strlen($this->data)));
        }
        
        $bytes = substr($this->data, $this->offset, 2);
        $this->offset += 2;
        
        return (ord($bytes[0]) << 8) | ord($bytes[1]);
    }

    /**
     * 读取uint16
     */
    public function uint16(): int
    {
        return $this->int16() & 0xFFFF;
    }

    /**
     * 读取指定长度的字节数组
     */
    public function bytes(int $num): string
    {
        if ($this->offset + $num > strlen($this->data)) {
            throw new \RuntimeException(sprintf('Decoder couldn\'t read expect bytes %d of %d', $this->offset + $num, strlen($this->data)));
        }
        
        $result = substr($this->data, $this->offset, $num);
        $this->offset += $num;
        return $result;
    }

    /**
     * 读取int32
     */
    public function int32(): int
    {
        if ($this->offset + 4 > strlen($this->data)) {
            throw new \RuntimeException(sprintf('Decoder couldn\'t read expect bytes %d of %d', $this->offset + 4, strlen($this->data)));
        }
        
        $bytes = substr($this->data, $this->offset, 4);
        $this->offset += 4;
        
        return (ord($bytes[0]) << 24) | (ord($bytes[1]) << 16) | (ord($bytes[2]) << 8) | ord($bytes[3]);
    }

    /**
     * 读取uint32
     */
    public function uint32(): int
    {
        return $this->int32() & 0xFFFFFFFF;
    }

    /**
     * 读取int64
     */
    public function int64(): int
    {
        if ($this->offset + 8 > strlen($this->data)) {
            throw new \RuntimeException(sprintf('Decoder couldn\'t read expect bytes %d of %d', $this->offset + 8, strlen($this->data)));
        }
        
        $bytes = substr($this->data, $this->offset, 8);
        $this->offset += 8;
        
        return (ord($bytes[0]) << 56) | (ord($bytes[1]) << 48) | (ord($bytes[2]) << 40) | (ord($bytes[3]) << 32) |
               (ord($bytes[4]) << 24) | (ord($bytes[5]) << 16) | (ord($bytes[6]) << 8) | ord($bytes[7]);
    }

    /**
     * 读取uint64
     */
    public function uint64(): int
    {
        return $this->int64();
    }

    /**
     * 读取字符串（先读取长度，再读取内容）
     */
    public function string(): string
    {
        $binary = $this->binary();
        return $binary;
    }

    /**
     * 读取二进制数据（先读取长度，再读取内容）
     */
    public function binary(): string
    {
        $size = $this->int16();
        if ($size < 0) {
            throw new \RuntimeException(sprintf('size is less than 0, size: %d', $size));
        }

        if ($this->offset + $size > strlen($this->data)) {
            throw new \RuntimeException(sprintf('Decoder couldn\'t read expect bytes %d of %d', $this->offset + $size, strlen($this->data)));
        }

        $result = substr($this->data, $this->offset, $size);
        $this->offset += $size;
        return $result;
    }

    /**
     * 读取所有剩余数据作为二进制
     */
    public function binaryAll(): string
    {
        $remains = $this->len();
        $result = substr($this->data, $this->offset);
        $this->offset += $remains;
        return $result;
    }

    /**
     * 读取所有剩余数据作为字符串
     */
    public function stringAll(): string
    {
        return $this->binaryAll();
    }

    /**
     * 读取可变长度整数
     */
    public function variable(): int
    {
        $size = 0;
        $mul = 1;
        
        while (true) {
            $byte = $this->uint8();
            $size += ($byte & 0x7F) * $mul;
            $mul *= 0x80;
            
            if (($byte & 0x80) === 0) {
                break;
            }
        }
        
        return $size;
    }

    /**
     * 获取当前偏移量
     */
    public function getOffset(): int
    {
        return $this->offset;
    }

    public function getData(): string
    {
        return $this->data;
    }

    /**
     * 设置偏移量
     */
    public function setOffset(int $offset): void
    {
        if ($offset < 0 || $offset > strlen($this->data)) {
            throw new \RuntimeException('Invalid offset');
        }
        $this->offset = $offset;
    }
}